<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>canvas</title>
    <style>
      body {
        display: flex;
        flex-flow: row wrap;
        overflow: hidden;
        margin: 0;
        padding: 0;
        position: relative;
      }
      #root {
        position: absolute;
        left: 50%;
        transform: translateX(-50%);
      }
      h1 {
        text-align: center;
      }
      .cursor {
        cursor: pointer;
      }
      .flex {
        display: flex;
        flex-flow: row nowrap;
        align-items: center;
      }
      .flex span {
        flex: 0 0 150px;
      }
      .margin {
        margin: 10px 0;
      }
      .control-place {
        padding: 10px 0;
      }
    </style>
    <link rel="stylesheet" href="./video.css" />
  </head>
  <body>
    <div id="root">
      <h1>{{title}}</h1>
      <div id="control-place" class="control-place">
        <div class="flex margin">
          <!-- (:model待完善) -->
          <span>开屏首帧图片来源:</span>
          <div>
            <label
              ><input
                type="radio"
                name="videoFirstFrame"
                value="1"
                @change="changeVideoFirstFrame"
              />视频首帧图片</label
            >
            <label
              ><input
                type="radio"
                name="videoFirstFrame"
                value="0"
                @change="changeVideoFirstFrame"
              />自定义图片</label
            >
          </div>
        </div>
        <!-- <div class="flex margin">
          <span>视频是否处理:</span>
          <div>
            <label
              ><input
                type="radio"
                name="videoHandle"
                value="1"
                @change="changeVideoHandle"
              />原视频</label
            >
            <label
              ><input
                type="radio"
                name="videoHandle"
                value="0"
                @change="changeVideoHandle"
              />处理后的视频</label
            >
          </div>
        </div> -->
        <div class="flex margin">
          填充字符：<input type="text" :model="showNumber" />
        </div>
        <div class="flex margin">
          视频效果：<label
            ><input
              type="radio"
              name="videoEffect"
              value="0"
              @change="changeVideoEffect"
            />反差模糊效果</label
          >
          <label
            ><input
              type="radio"
              name="videoEffect"
              value="1"
              checked="true"
              @change="changeVideoEffect"
            />黑白照片</label
          >
        </div>
      </div>
      <!-- <div>
        <button @click="exportVideo">导出视频</button>
      </div> -->
      <div class="flex">
        <div>
          <h2 class="center">处理后视频</h2>
          <div id="video-player" class="video-player">
            <video class="video-player_video opacity_0" :src="videoUrl" @click="togglePlay"></video>
            <canvas @click="togglePlay"></canvas>
            <!-- <div class="video-player_bottom"></div> -->
            <div class="video-player_controls">
              <button
                :class="['video-player_control-btn', state.isPlaying ? 'pause' : 'play']"
                @click="togglePlay"
              ></button>
              <!-- timeFormat(state.currentTime) -->
              <span>{{state.currentTime}}</span>
              <div
                id="video-player_progress-bar"
                class="video-player_progress-bar cursor"
                @click="changeProgress"
              >
                <div
                  class="video-player_progress"
                  :style="{ width: state.progress + '%', height: '100%' }"
                ></div>
              </div>
              <span>{{state.duration}}</span>
              <div class="video-player_volume-bar">
                <button class="video-player_control-btn" @click="toggleVolume">
                  音量
                </button>
                <input
                  class="cursor"
                  type="range"
                  :if="state.isVolumeVisible"
                  :model="state.volume"
                  min="0"
                  max="1"
                  step="0.1"
                  @change="changeVolume"
                />
              </div>
              <div class="video-player_fullScreen">
                {{state.fullScreen ? '退出全屏' : '全屏'}}
              </div>  
            </div>
          </div>
        </div>
        <!-- <div>
          <h2 class="center">原视频</h2>
          <div class="video-player">
            <video id="display-player" class="video-player_video" autoplay></video>
          </div>
        </div> -->
      </div>
    </div>
    <script type="module">
      import { Component } from "../core/index.js";
      const component = new Component({
        data: () => {
          return {
            showNumber: 1,
            player: {}, // 播放器盒子对象
            cvs: {}, // 画布
            video: {}, // 视频对象
            displayPlayer: {},
            progressBar: {}, // 视频进度条父元素
            ctx: {}, // 画布对象
            title: "LveVideoJs",
            videoUrl: "./dy.mp4",
            state: {
              // 播放状态
              isPlaying: false,
              // 进度 0-100
              progress: 0,
              // 音量 0-1
              volume: 0.5,
              // 是否隐藏音量键
              isVolumeVisible: true,
              // 总时长
              duration: 0,
              // 目前播放时间
              currentTime: 0,
              // 是否全屏
              fullScreen: false,
            },
            // 是否展示视频第一帧还是自定义图片
            displayFirstFrame: 1,
            // 视频是否处理
            videoHandle: 0,
            videoEffect: 1,
            stream: {},
            recordeBlobs: [], // 二进制视频文件
            mediaRecorder: {} // 媒体对象
          };
        },
        watch: {},
        mounted() {
          this.initVideo();
          this.showFrameOrCustomImage();
        },
        methods: {
          /** 更改开屏首帧图片来源 */
          changeVideoFirstFrame(e) {
            this.displayFirstFrame = Number(e.target.value);
          },
          changeVideoEffect(e) {
            this.videoEffect = Number(e.target.value);
          },
          changeVideoHandle(e) {
            this.videoHandle = Number(e.target.value);
          },
          /** 选择显示视频第一帧或者自定义图片 */
          showFrameOrCustomImage() {
            /** 下载完当前帧数据时触发 */
            this.video.addEventListener("loadeddata", () => {
              // 判断是否要显示第一帧
              if (this.displayFirstFrame) {
                // 设置视频当前时间为第一帧（0秒）
                this.video.currentTime = 0;
                // 暂停视频
                this.video.pause();
                setTimeout(() => {
                  this.paintCanvas();
                }, 50);
                // 展示自定义图片
              } else {
                const customImageURL = "./lddy.jpg"; // 根据需求设定
                this.paintCanvas(customImageURL);
              }
            });
            this.video.load();
          },
          /** 初始化视频 */
          initVideo() {
            this.player = document.querySelector("#video-player");
            this.video = document.querySelector("#video-player video");
            this.displayPlayer = document.querySelector('#display-player');
            this.progressBar = document.querySelector(
              "#video-player_progress-bar"
            );
            /** 下载完元数据时候触发 */
            this.video.addEventListener("loadedmetadata", () => {
              this.cvs = document.querySelector("#video-player canvas");
              this.cvs.style.display = "block";
              this.ctx = this.cvs.getContext("2d");
              // 获取视频总时长
              this.state.duration = Math.ceil(this.video.duration);
              // 输出视频元素的宽度和高度
              this.cvs.height = this.video.offsetHeight;
              this.cvs.width =
                (this.video.videoWidth * this.video.offsetHeight) /
                this.video.videoHeight;
            });
            /** 视频播放结束时触发 */
            this.video.addEventListener("ended", () => {
              this.playerEnd();
            });
          },
          /** 开始播放视频 */
          start() {
            this.state.isPlaying = true;
            this.video.play();
            requestAnimationFrame(this.updateProgress.bind(this));
            // this.stream = this.cvs.captureStream(25);
            // this.video.srcObject = this.stream; // 屏幕实时回显
            this.video.onplay = () => {
              const stream = this.video.captureStream();
              this.displayPlayer.srcObject = stream;
            }
            // const isSupported = this.isSupportedRecord() // 判断当前浏览器是否能进行录制
            // if (isSupported) { // 能录制就进行录制，不能录制也不会影响当前播放功能
            //   this.recordVideo(this.stream)
            // }
          },
          /** 暂停播放视频 */
          pause() {
            this.state.isPlaying = false;
            this.video.pause();
          },
          /** 更新当前视频进度 */
          updateProgress() {
            // 更新进度条
            this.state.progress =
              (this.video.currentTime / this.state.duration) * 100;
            // 更新当前时间
            this.state.currentTime = Math.ceil(this.video.currentTime);
            // 具体绘制操作
            if (!this.videoHandle) {
              this.cvs.style.display !== "block" &&
                (this.cvs.style.display = "block");
              this.video.style.opacity = 0;
              this.paintCanvas();
            } else {
              this.cvs.style.display = "none";
              this.video.style.opacity = 1;
            }
            // 当处于播放状态时，才执行RAF
            if (this.state.isPlaying) {
              requestAnimationFrame(this.updateProgress.bind(this));
            }
          },
          /** 绘制canvas */
          paintCanvas(from = "video") {
            const { width, height } = this.cvs;
            const canvas = document.createElement("canvas");
            canvas.width = width;
            canvas.height = height;
            const context = canvas.getContext("2d");
            new Promise((resolve, reject) => {
              if (from === "video") {
                context.drawImage(this.video, 0, 0, width, height);
                resolve();
              } else {
                const image = new Image();
                image.src = from;
                image.onload = () => {
                  context.drawImage(image, 0, 0, width, height);
                  resolve();
                };
              }
            }).then(() => {
              const imageData = context.getImageData(0, 0, width, height);
              const data = imageData.data
              switch (this.videoEffect) {
                case 0:
                  this.videoEffect0(data, width, height);
                  break;
                case 1:
                  this.videoEffect1(imageData, width, height);
                  break;
              }
            });
          },
          videoEffect0(data, width, height) {
            const bl = 4;
            this.ctx.clearRect(0, 0, width, height);
            for (let i = 0; i < data.length; i += 4 * bl) {
              const x = parseInt(((i + 1) % (width * 4)) / 4);
              const y = parseInt(i / (width * 4));
              if (x % bl === 0 && y % bl === 0) {
                const g = parseInt(
                  (data[i] + data[i + 1] + data[i + 2]) / 1.5
                );
                this.ctx.fillStyle = `rgba(${g}, ${g}, ${g}, ${data[i + 3]})`;
                this.ctx.fillText(this.showNumber, x, y);
              }
            }
          },
          videoEffect1(imageData, width, height) {
            const data = imageData.data
            for (let i = 0; i < data.length; i += 4) {
              const brightness = (data[i] + data[i + 1] + data[i + 2]) / 3; // 计算亮度值
              data[i] = brightness; // 设置红色通道
              data[i + 1] = brightness; // 设置绿色通道
              data[i + 2] = brightness; // 设置蓝色通道
            }
            // 将新的 ImageData 对象绘制到 Canvas 上
            this.ctx.putImageData(imageData, 0, 0);
          },
          /** 开始/暂停操作 */
          togglePlay() {
            this.state.isPlaying = !this.state.isPlaying;
            if (this.state.isPlaying) {
              this.start();
              // this.video.requestFullscreen()
            } else {
              this.pause();
            }
          },
          /** 显示隐藏音量进度条 */
          toggleVolume() {
            this.state.isVolumeVisible = !this.state.isVolumeVisible;
          },
          /** 改变音量进度 */
          changeVolume(e) {
            this.state.volume = this.video.volume = e.target.value;
          },
          /** 改变视频进度 */
          changeProgress(e) {
            // 先暂停, 更新进度和时间后，再开始
            this.pause();
            const percent = Math.round(
              (e.offsetX / this.progressBar.offsetWidth) * 100
            );
            this.state.progress = percent;
            this.state.currentTime = this.video.currentTime =
              (this.state.duration * percent) / 100;
            this.start();
          },
          /** 视频播放完毕 */
          playerEnd() {
            this.state.isPlaying = false;
            this.state.currentTime = this.video.currentTime =
              this.state.duration;
            this.state.progress = 100;
            this.video.removeEventListener("ended", () => {
              this.playerEnd();
            });
          },
          /** 秒数格式化 */
          timeFormat(s) {
            const minute = parseInt((s / 60) % 60);
            const hour =
              parseInt(s / 3600) < 10
                ? "0" + parseInt(s / 3600)
                : parseInt(s / 3600);
            const second = Math.ceil(s % 60) > 59 ? 59 : Math.ceil(s % 60);
            return `${hour > 0 ? `${hour}:` : ""}${
              minute < 10 ? "0" + minute : minute
            }:${second < 10 ? "0" + second : second}`;
          },
          /**
           * 视频录制中
           **/
          recordVideo(stream) {
            this.recordeBlobs = [];
            if (!this.mediaRecorder) {
              this.mediaRecorder = new MediaRecorder(stream, {
                mimeType: 'video/webm;codecs=vp8',
              });
              this.mediaRecorder.onstop = (e) => {
                console.log('录制完成');
              };
              this.mediaRecorder.ondataavailable = (e) => {
                if (e.data && e.data.size > 0) {
                  this.recordeBlobs.push(e.data);
                }
              };
              this.mediaRecorder.start(100);
            }
          },
          /**
           * 导出视频文件
           **/
          exportVideo() {
            if (!this.mediaRecorder) {
              console.warn('LveRecord: 录制对象没有被初始化，请确定你的浏览器是否支持录制')
              return
            }
            this.mediaRecorder.stop();
            if (this.recordeBlobs.length === 0) {
              console.warn('LveRecord: 无录制后文件')
              return
            }
            const blob = new Blob(this.recordeBlobs, { type: 'video/webm;codecs=vp8' });
            const url = window.URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.style.display = 'none';
            a.href = url;
            a.download = 'record.mp4';
            document.body.appendChild(a);
            a.click();
            setTimeout(() => {
              document.body.removeChild(a);
              window.URL.revokeObjectURL(url);
            }, 100);
          },
          /**
           * 判断其 MIME 格式能否被客户端录制
           **/
          isSupportedRecord()  {
            const mimeType = 'video/webm;codecs=vp8'
            return MediaRecorder.isTypeSupported(mimeType)
          }
        },
      });
      const player = component.mount("#root");
    </script>
  </body>
</html>
